//-----------------------------------------------------------------------
// <copyright file="Update.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// </copyright>
// <author>Andy Reeves</author>
// <email>andyr</email>
// <date>2004-03-23</date>
// <summary>Updates a versionnumber.exe.config file to the correct number</summary>
//-----------------------------------------------------------------------

namespace Microsoft.Sdc.Tasks.VersionNumber
{
	using System;
	using System.Xml;
	using System.Globalization;
	using System.IO;
	using Microsoft.Win32;
	using Microsoft.Sdc.Tasks.Configuration;
	using Microsoft.Build.Framework;

	/// <summary>
	/// Updates a versionnumber.exe.config file to the correct number
	/// </summary>
	/// <remarks>
	/// Note. This checks out the file in question from Source Control, updates the number and then checks the updated file back in.
	/// It only works with Source Depot currently.
	/// <code><![CDATA[
	/// <VersionNumber.Update
	///             ConfigFileLocation="configFileLocation"
	///             OnlyIncrementRevision="onlyIncrementRevision"
	///             <Output TaskParameter="VersionNumber" ItemName="versionNumberItem"></Output>
	/// </VersionNumber.Update>
	/// ]]></code>
	/// <para>where:</para>
	/// <para><i>configFileLocation (Required)</i></para>
	/// <para>Fully qualified file name to the xml config file. The file contains, in Xml format, the major, minor, build and revision number.</para>
	/// <para>
	/// For example:
	/// 
	/// <example>
	/// <code><![CDATA[
	/// <?xml version="1.0" encoding="utf-8"?>
	///     <configuration>
	///         <appSettings>
	///             <add key="major" value="1" />
	///             <add key="minor" value="2" />
	///             <add key="build" value="1354" />
	///             <add key="revision" value="155" />
	///         </appSettings>
	///     </configuration>
	/// ]]></code>    
	/// </example>
	/// </para>
	/// <para><i>onlyIncremementRevision</i></para>
	/// <para>Force Revision only change. So 2.0.1188.14 would be 2.0.1188.15 on the next automatic label from the source control system.</para>
	/// </remarks>
	/// <example>
	/// <code><![CDATA[
	/// <Project>
	///     <Target Name="Test" >
	///         <VersionNumber.Update VersionNumberConfigFileLocation="@(VersionXmlFile)">
	///             <Output TaskParameter="VersionNumber" ItemName="VersionNumber" />
	///         </VersionNumber.Update>
	///     </Target>
	/// </Project>
	/// ]]></code>    
	/// </example>

	public class VSSUpdate : TaskBase
	{
		private string buildNumberFull;
		private string configFileLocation;
		private string configVssLocation;
		private string versionNumberFormat; 
		private DateTime startDate = new DateTime(2000, 1, 1);
		private string buildBuildNumber;
		private bool onlyIncrementRevision;
		private bool onlyIncrementBuild;
		private string revisionBuildNumber;
		private int majorBuildNumber;
		private int minorBuildNumber;
		private bool skipSourceControl = false;
		private bool useVss = false;
		private string database;
		private string username;
		private string password;

		private XmlDocument versionConfig = new XmlDocument();

		/// <summary>
		/// Initializes a new instance of the UpdateVersionNumber class.
		/// </summary>
		public VSSUpdate()
		{
		}

		/// <summary>
		/// TODO
		/// </summary>
		/// <value>TODO</value>
		[Output]
		public string VersionNumber
		{
			get
			{
				return (buildNumberFull == null ? String.Empty : buildNumberFull);

			}
			set
			{
				//Setter must be here but don't do anything with the value passed in
			}
		}

		public string ConfigFileLocation
		{
			get
			{
				return (this.configFileLocation == null ? String.Empty : this.configFileLocation);
			}
			set
			{
				this.configFileLocation = value;
			}
		}

		/// <summary>
		/// If specified then this value represents the location in VSS of the VersionNumber file.
		/// </summary>
		/// <value></value>
		public string ConfigVssLocation
		{
			get
			{
				return (this.configVssLocation == null ? String.Empty : this.configVssLocation);
			}
			set
			{
				this.configVssLocation = value;
			}
		}

		/// <summary>
		/// If TRUE doesn't checkout or submit to source control
		/// </summary>
		/// <value></value>
		public bool SkipSourceControl
		{
			get
			{
				return this.skipSourceControl;
			}
			set
			{
				this.skipSourceControl = value;
			}
		}

		/// <summary>
		/// If TRUE uses VSS for source control
		/// </summary>
		/// <value></value>
		public bool UseVss
		{
			get
			{
				return this.useVss;
			}
			set
			{
				this.useVss = value;
			}
		}

		/// <summary>
		/// If TRUE only the build part of the build number will be incremented
		/// </summary>
		/// <value>Force Build increment change. So 2.0.1188.0 would be 2.0.1189.0 on the next automatic label from the source control system.</value>
		public bool OnlyIncrementBuild
		{
			get
			{
				return this.onlyIncrementBuild;
			}
			set
			{
				this.onlyIncrementBuild = value;
			}
		}      

		/// <summary>
		/// If TRUE only the revision part of the build number will be incremented
		/// </summary>
		/// <value>Force Revision only change. So 2.0.1188.14 would be 2.0.1188.15 on the next automatic label from the source control system.</value>
		public bool OnlyIncrementRevision
		{
			get
			{
				return this.onlyIncrementRevision;
			}
			set
			{
				this.onlyIncrementRevision = value;
			}
		}      

		/// <summary>
		/// If specified then this value indicates how the build number is calculated. i.e. a format of yyMMdd on July 15th ,2004 would give 040715. Note, this must only contain valid date formatting characters.
		/// If not specified the the build number format is the number of days since startDate from the xmlFile. If startDate is not specified then it defaults to Jan 1st, 2000.
		/// </summary>
		/// <value></value>
		public string VersionNumberFormat
		{
			get
			{
				return (this.versionNumberFormat == null ? String.Empty : this.versionNumberFormat);
			}

			set
			{
				versionNumberFormat = value;
			}
		}

		/// <summary>
		/// If specified then this value represents the VSS database location in VSS format e.g. V:\SourceSafe.
		/// </summary>
		/// <value></value>
		public string Database
		{
			get
			{
				return (database == null ? String.Empty : database);
			}
			set
			{
				database = value;
			}
		}

		/// <summary>
		/// If specified then this value represents the VSS username that has access to the database.
		/// </summary>
		/// <value></value>
		public string Username
		{
			get
			{
				return (username == null ? String.Empty : username);
			}
			set
			{
				username = value;
			}
		}

		/// <summary>
		/// If specified then this value represents the password for the VSS username specified.
		/// </summary>
		/// <value></value>
		public string Password
		{
			get
			{
				return (password == null ? String.Empty : password);
			}
			set
			{
				password = value;
			}
		}

		/// <summary>
		/// TODO
		/// </summary>
		/// <summary>
		/// Performs the action of this task.
		/// </summary>
		protected override void InternalExecute()
		{
			if (this.SkipSourceControl)
			{
				this.IncrementVersionNumbers();
			}

			if (!this.SkipSourceControl && this.useVss)
			{
				RegistryKey vssKey = null;
				string pathToSourceSafe = "";

				vssKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\VisualStudio\6.0\Setup\Microsoft Visual SourceSafe Server");
				if (vssKey == null)
				{
					//couldn't find the Server key so try the other one
					vssKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\VisualStudio\6.0\Setup\Microsoft Visual SourceSafe");
				}

				ShellExecute shellExecute = new ShellExecute();
				if (vssKey != null)
				{
					pathToSourceSafe = Convert.ToString(vssKey.GetValue("ProductDir"), CultureInfo.InvariantCulture);
					shellExecute.Filename = Path.Combine(pathToSourceSafe, @"win32\ss.exe");
					vssKey.Close();
				}
				else
				{
					shellExecute.Filename = "ss.exe";
				}

				shellExecute.EnvironmentVariables["SSDIR"] = database;
				if (username.Length == 0)
				{
					username = Environment.UserName;
				}
				FileInfo fi = new FileInfo(this.configFileLocation);
				fi.Attributes = FileAttributes.Normal;
				fi.Delete();

				string args = String.Format(CultureInfo.InvariantCulture, "Checkout -GL{0} -I-N -O@{1}\\VSSCheckout.txt -Y{2},{3} {4}",
					fi.DirectoryName,
					Path.Combine(Environment.CurrentDirectory, "Output"),
					username,
					password,
					configVssLocation);

				Log.LogMessageFromResources("Vss.Connect");
				Log.LogMessageFromResources("Vss.CheckOut", this.configVssLocation);

				shellExecute.Arguments = args;
				shellExecute.Execute();
				Log.LogMessage(shellExecute.StandardError);
	
				this.IncrementVersionNumbers();

				args = String.Format(CultureInfo.InvariantCulture, "Checkin -GL{0} -I-N -O@{1}\\VSSCheckin.txt -Y{2},{3} {4}",
				fi.DirectoryName,
				Path.Combine(Environment.CurrentDirectory, "Output"),
				username,
				password,
				configVssLocation);

				Log.LogMessageFromResources("Vss.Connect");
				Log.LogMessageFromResources("Vss.CheckIn", this.configVssLocation);

				shellExecute.Arguments = args;
				shellExecute.Execute();
				Log.LogMessage(shellExecute.StandardError);
			}


			if (!this.SkipSourceControl && !this.useVss)
			{
				Utilities.SdCmd("edit " + this.configFileLocation, "AUTO Checking out the VersionNumber file...");
			
				this.IncrementVersionNumbers();
				
				Utilities.SdCmd("submit -C \"AUTO " + this.buildNumberFull + " revising build number file\"", "Updating build number...");

			}
		}

		private string CalculateNewBuildNumber()
		{
			string newBuild;

			if (this.versionNumberFormat == null || this.versionNumberFormat.Length == 0)
			{
				newBuild = Convert.ToString((DateTime.Now - this.startDate).Days);
			}
			else
			{
				newBuild = DateTime.Today.ToString(this.versionNumberFormat);
			}
			return newBuild;
		}
		/// <summary>
		/// TODO
		/// </summary>
		private void IncrementVersionNumbers()
		{
			this.LoadVersionNumber();

			string newBuild = CalculateNewBuildNumber();
			if (this.onlyIncrementBuild)
			{
				int build = Convert.ToInt32(this.buildBuildNumber);
				build++;
				this.buildBuildNumber = Convert.ToString(build);
				int revisionLength = this.revisionBuildNumber.Length;
				this.revisionBuildNumber = "0".PadLeft(revisionLength, '0');
			}
			else
			{
				if ( (Convert.ToInt32(newBuild) > Convert.ToInt32(this.buildBuildNumber)) && !this.onlyIncrementRevision)
				{
					this.buildBuildNumber = newBuild;
					int revisionLength = this.revisionBuildNumber.Length;
					this.revisionBuildNumber = "0".PadLeft(revisionLength, '0');
				}
				else
				{
					int revision = Convert.ToInt32(this.revisionBuildNumber);
					revision++;
					int revisionLength = this.revisionBuildNumber.Length;
					this.revisionBuildNumber = Convert.ToString(revision).PadLeft(revisionLength, '0');
				}
			}

			this.SaveVersionNumber();
		}

		/// <summary>
		/// TODO
		/// </summary>
		private void LoadVersionNumber()
		{
			this.versionConfig.Load(this.configFileLocation);

			XmlNode majorNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='major']");
			if (majorNode != null)
			{
				this.majorBuildNumber = XmlConvert.ToInt32(majorNode.Attributes["value"].Value);
			}

			XmlNode minorNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='minor']");
			if (minorNode != null)
			{
				this.minorBuildNumber = XmlConvert.ToInt32(minorNode.Attributes["value"].Value);
			}

			XmlNode buildNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='build']");
			if (buildNode != null)
			{
				this.buildBuildNumber = buildNode.Attributes["value"].Value;
			}

			XmlNode revisionNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='revision']");
			if (revisionNode != null)
			{
				this.revisionBuildNumber = revisionNode.Attributes["value"].Value;
			}

			XmlNode startDateNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='startDate']");
			if (startDateNode != null)
			{
				this.startDate = XmlConvert.ToDateTime(startDateNode.Attributes["value"].Value,"yyyy-MM-dd");
			}

			this.buildNumberFull = String.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}.{3}", this.majorBuildNumber, this.minorBuildNumber, this.buildBuildNumber, this.revisionBuildNumber);
		}

		/// <summary>
		/// TODO
		/// </summary>
		private void SaveVersionNumber()
		{
			XmlNode majorNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='major']");
			XmlNode minorNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='minor']");
			XmlNode buildNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='build']");
			XmlNode revisionNode = this.versionConfig.SelectSingleNode("configuration/appSettings/add[@key='revision']");

			majorNode.Attributes["value"].InnerText = XmlConvert.ToString(this.majorBuildNumber);
			minorNode.Attributes["value"].InnerText = XmlConvert.ToString(this.minorBuildNumber);
			buildNode.Attributes["value"].InnerText = this.buildBuildNumber;
			revisionNode.Attributes["value"].InnerText = this.revisionBuildNumber;

			this.buildNumberFull = String.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}.{3}", this.majorBuildNumber, this.minorBuildNumber, this.buildBuildNumber, this.revisionBuildNumber);
			this.versionConfig.Save(this.configFileLocation);
		}        
	}
}